8章 防御的プログラミング
8.3 qst_exe.icon
Overview
防御的プログラミングは、防御運転にヒントを得たもの。他責のことについても自分に被害が及ばないようにするための考え方である。
外部APIや外部サービス使ってるときによくありがち
8.1 無効な入力への防御
「ごみ入れ、ごみ出し」(garbagein,garbageout Steve McConnel)という表現を聞いたことがあるだろうか。この表現は、基本的に、「買い手危険負担」のソフトウェア開発バージョンであり、ユーザーに警戒を促すものだ。
聞いたことなかった qst_exe.icon
外部ソースからのデータ確認
ちょうど昨日SQLインジェクションがあったところ
ルーチンの入力引数の確認
バリデーションとか
不正な入力を処理する方法を決定する
8.3 で詳しく解説
8.2 アサーション
アサーションは、開発時にプログラムの実行をプログラム単位で検査するコードである。
確認する条件の例
入力引数の値、出力引数の値が期待される範囲内かどうか
ルーチンの開始、終了時のファイルの開閉
ルーチンが入力専用の引数の値を変更していないか
ポインタがnullでないか
テーブルが初期化されて、実際の値を保持できるようになっているか
8.2.1 独自のアサーションの構築
言語によってはアサーションがないものがあるので、それを独自に構築した話
8.2.2 アサーションのガイドライン
アサーションは絶対に発生してはいけない状況の確認に使う
アサーションには実行コードを含めずに結果をアサーションする
assert(ルーチxン, "メッセージ" )はダメ
アサーションの検証方法には、事前条件と事後条件の確認
事前条件…ルーチンが呼び出されたり、オブジェクトがインスタンス化される前に真である特性のこと。クライアントコードがコードを呼び出すための義務。
事後条件…ルーチンやクラスの実行終了時に真である特性のこと。クライアントコードに対するルーチンやクラスの義務。
堅牢性の高いコードは、エラーをアサートしてから処理する
Wordの場合は、常に真となる条件をアサートしているうえ、エラーはアサーションが失敗した場合に備えてエラー処理を行っている(その方法は8.3で紹介)
アサーションは本番用では実行しない(開発用のみ)
8.3 エラー処理のテクニック
状況によって処理方法は変わり、場合によってはそれらを組み合わせる場合がある
当たり障りのない値を返す
当たり障りのない値を表示するのがまずい場合(病院の診断データなど)は、プログラムを終了させる
有効なデータで代用する
前回と同じ値を返す
有効な値のうち、最も近いもので代用する
車のバックの例(速度的にはマイナスだけどメーターで表示できないので0km/hにしている)
ファイルに警告メッセージを記録する
エラーコードを返す
エラー処理ルーチン、オブジェクトを呼び出す
エラーの発生個所でオブジェクトを呼び出す
ローカルで最も上手くいく方法で処理を返す
処理を中止する
8.3.1 堅牢(けんろう)性と正当性
正当性
不正確な結果を決して返さないこと(ex. レントゲン)
堅牢性
ソフトウェアの実行をできるように手を尽くすこと(ex. Word)
同じソフトでも用途によって、使い分けてた qst_exe.icon
ゲームの間に強制的に入る動画広告
視聴することでコインが手に入る動画広告
8.3.2 上位レベルのエラー処理
エラー処理は選択肢が多いので、プログラム全体で不正なパラメータが一貫した方法で処理されるように注意しなければならない
エラーの処理方法はソフトウェアが、正当性、堅牢性、その他の非機能要件に関する条件を満たすかに影響する
エラーの処理方法を決定したらそれを守らなければいけない
DBへのアクセスとかかな
C++では関数がエラーを返しても無視できるものがある
luaにも似たような機能があった気がする
8.4 moch5oMaki.icon
8.4 例外
例外処理を適切に使おうね、という話。例外の使い方のプラクティスとアンチパターンが紹介されている。
例外の利点:継承と共通した属性があり、使いようによって複雑さを軽減できること
例外の欠点:使い方を誤るとコードを理解するのがほぼ不可能になってしまう(くらいに複雑さが増す)
例外が正常な処理の一部として使われているプログラムは、スパゲッティコードが持つ可読性や保守性の問題から逃れられない -Andy Hunt
例外を例外として扱わないと大変なことになるぞ、とmoch5oMaki.icon
言語による例外の違い
PHPだとバージョンによって例外の扱いが違うとかあるけど、他の言語もそうなのかなmoch5oMaki.icon
ここからは例外の使い方について(書籍より項目のみ列挙)
無視すべきでないエラーは例外を使用してプログラムの他の部分に伝える
本当に例外的な状況でのみ例外をスローする
アサーションと同じような状況、つまり絶対に発生してはならないイベントに対して使う
例外は予想外の状況に対処する強力な手段とコードの複雑さの増大のトレードオフ
例外を責任逃れに使用しない
その場でキャッチする場合をのぞき、コンストラクタとデストラクタで例外をスローしない
正しい抽象化レベルで例外をスローする
例外メッセージに例外の原因に関する全ての情報を盛り込む
空のcatchブロックを書かない
ライブラリコードがスローする例外を知る
例外レポート用ルーチンでの集中管理を検討する
プロジェクトで例外の使用法を標準化する
moch5oMaki.iconこういうのって設計段階でちゃんとやるんですか?→qst_exe.iconakht.icon
なんとなく使うより、確かに決めておいた方が使いやすくはなりそう
型が明確な言語だとより使いやすそう
例外に代わる手段を検討する
例外処理が本当に必要かどうかを検討する、ということ
エラー処理の全ての可能性(ローカルで処理、エラーコードを使う、ファイルにデバッグ情報を記録する、システムをシャットダウンする、、、etc)を検討する
言語が例外処理に対応しているという理由だけで、例外を使ってエラーを処理することは、言語の中へのプログラミングではなく、言語の中でのプログラミングの典型的な例である
本書では言語の中へのプログラミングを推奨している(4.3.1 参照)
言語の仕様によらず普遍的で重要なプログラミング原理を使ってプログラムを書くこと
使用したい構造が言語に含まれていない、またはなんらかの問題が起きやすい、という時に、コーディング規約、標準、クラスライブラリなどを独自に補強する方法を考案することで解決を目指す考え方
moch5oMaki.iconこれ実際どうなんでしょうね?
言語の進化が早いので便利なやつは結構横展開されてる印象もある
オレオレで書いた結果、言語のバージョンアップについていけない、なんてことはないのだろうか
8.5 バリケードによるエラー被害の囲い込み
入力→バリケード→内部クラスという構造を作って、クラスにデータを渡すときは、消毒された データを渡しましょう、という話
入力データは入力されたときに正しい方に変換する
バリケードとアサーションの関係
バリケードの外側はダーティなのでエラー処理を使う
バリケードの内側はクリーンなのでアサーションを使う
前回から、アサーションがふわっとしてたのでちょっと調べた
アサーションの使用が役に立つ状況は数多くあります。 このセクションでは、それらの状況についていくつか説明します。
・内部の不変条件
・制御フローの不変条件
・事前条件、事後条件、およびクラスの不変条件
この条件を調べてると、「契約による設計(Design by Contract)」と関連が深そうでした
で、契約プログラミングを調べていてこの記事を見つけて、言語によってはアサーションは使わない、みたいに書いてたのでそんもんかぁ、という感じ(Javaではよく使うのかな) 表明とは、
プログラミングにおける概念のひとつであり、そのプログラムの前提条件を示すのに使われる。アサーションとも呼ばれる。表明は、プログラムのその箇所で必ず真であるべき式の形式をとる。
8.6 デバッグエイド
便利なデバッグツールは使いましょう、開発バージョンと製品版のソフトウェアは制約が全く一緒じゃないので臨機応変にやりましょう、という話
攻撃的プログラミングの使用
攻撃的プログラミングとは、例外的な状況を開発時に明確にしておき、製品版ではコードの実行中に回復できるようにするように処理すること。
アサーションを使ったりcaseのdefault句でプログラムを終了させたりして、エンジニアが見て見ぬふりをしないようにすることで強制的に問題を修正させる(moch5oMaki.icon確かにエンジニアに対して攻撃的w)
基本的にはエラーをしっかり認知して改善するためにエラー発生時の挙動を攻撃的にしておく感じ
デバッグエイドの削除計画
開発モードと製品モードでそれぞれのビルドツールを使うとか、プリプロセッサでデバッグモードを切り替えるとか
8.7 製品コードに防御的プログラミングをどれだけ残すか
防御的プログラミングの矛盾点=開発段階ではエラーを目立たせたいが製品段階ではできるだけ上品に終わらせたい、というのをどう両立するかのガイドライン
重要なエラーを検査するコードは残す
ささいなエラーを検査するコードは削除する
処理を中断するようなコードは削除する
プログラムを上品にクラッシュさせるコードは残す
テクニカルサポート担当者のためのエラーを記録する
わかりやすいエラーメッセージは残す
8.8 防御的プログラミングに対する防御
あまりにも防御的なプログラミングもそれはそれで問題、やりすぎも良くない。プログラムは肥大化して低速になり、複雑さが増す。
ということで、どこでどこまで防御的になる必要があるか、のチェックリスト(ほとんどこの章で言ってきたことなのでリストそのものは割愛)
最後に急にセキュリティの項目が出たのでそれだけ書いておく
不正なデータを検査するコードは、バッファオーバーフロー、こっそり仕込まれたSQLコマンドやHTMLコード、整数の桁あふれ、その他の悪質な入力の試みをチェックしているか
エラーを返すコードを全てチェックしているか
全ての例外をキャッチしているか
エラーメッセージがシステムへの侵入を手助けするような情報を提供していないか
8.9 参考資料
防御的プログラミングの参考文献
8.10 まとめ
製品版のコードでは、「ごみ入れ、ごみ出し」よりも洗練された方法でエラーを処理すべきである。
防御的プログラミングテクニックは、エラーの検出と修正を容易にし、製品版へのコードの被害を食い止める。
特に大規模なシステム、高い信頼性が要求されるシステム、変化の速いコードベースでは、アサーションはエラーを早期に検出するのに役立つ。
不正な入力に対処する方法を決定することは、エラー処理においても上位レベルの設計においても重要な決断である。
例外は、コードの正常な流れとは別の次元で、エラー処理を実行するための手段となる。慎重に使用すれば、例外はプログラマの知的な道具箱になくてはならないツールとなるが、他のエラー処理テクニックと比較検討すべきである。
製品版のシステムに適用される制約を開発バージョンのシステムに適用する必要はない。開発バージョンは自分の都合の良いように使用することができ、エラーを素早く暴き出すためのコードを追加してもかまわない。